#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <dirent.h>
#include <errno.h>
#include <winsock2.h>
#include <shellapi.h>
#include <sys/stat.h>
#include <lm.h>

#include "pginstca.h"
#include "pgmsi.h"
#include "locale.h"

#include "libpq-fe.h"

static int RunSqlScript(MSIHANDLE hInstall, PGconn *conn, char *path, char *script);

/**************************************************************************/


/* This code is directly from initdb.c */
/*
 * make sure the data directory either doesn't exist or is empty
 *
 * Returns 0 if nonexistent, 1 if exists and empty, 2 if not empty,
 * or -1 if trouble accessing directory
 */
static int
check_data_dir(char *dir)
{
    DIR           *chkdir;
    struct dirent *file;
    int            result = 1;

    errno = 0;

    chkdir = opendir(dir);

    if (!chkdir)
        return (errno == ENOENT) ? 0 : -1;

    while ((file = readdir(chkdir)) != NULL)
    {
        if (strcmp(".", file->d_name) == 0 || strcmp("..", file->d_name) == 0)
        {
            /* skip this and parent directory */
            continue;
        }
        else
        {
            result = 2;            /* not empty */
            break;
        }
    }

    closedir(chkdir);

    if (errno != 0)
        result = -1;            /* some kind of I/O error? */

    return result;
}

/*
 *  Also ripped from initdb, with changes to error handling.
 */
static char **
replace_token(char **lines, char *token, char *replacement)
{
    int        numlines = 1;
    int        i;
    char       **result;
    int        toklen,
               replen,
               diff;

    for (i = 0; lines[i]; i++)
        numlines++;

    result = (char **) malloc(numlines * sizeof(char *));
    if (!result)
        return NULL;

    toklen = strlen(token);
    replen = strlen(replacement);
    diff = replen - toklen;

    for (i = 0; i < numlines; i++)
    {
        char    *where;
        char    *newline;
        int     pre;

        /* just copy pointer if NULL or no change needed */

        if (lines[i] == NULL || (where = strstr(lines[i], token)) == NULL)
        {
            result[i] = lines[i];
            continue;
        }

        /* if we get here a change is needed - set up new line */

        newline = (char *) malloc(strlen(lines[i]) + diff + 1);
        if (!newline)
            return NULL;

        pre = where - lines[i];

        strncpy(newline, lines[i], pre);

        strcpy(newline + pre, replacement);

        strcpy(newline + pre + replen, lines[i] + pre + toklen);

        result[i] = newline;

    }

    return result;

}

/*
 *  Also ripped from initdb, with changes to error handling.
 */
static char **
readfile(char *path)
{
    FILE       *infile;
    int        maxlength = 0,
               linelen = 0;
    int        nlines = 0;
    char       **result;
    char       *buffer;
    int        c;

    if ((infile = fopen(path, "r")) == NULL)
        return NULL;

    /* pass over the file twice - the first time to size the result */

    while ((c = fgetc(infile)) != EOF)
    {
        linelen++;
        if (c == '\n')
        {
            nlines++;
            if (linelen > maxlength)
                maxlength = linelen;
            linelen = 0;
        }
    }

    /* handle last line without a terminating newline (yuck) */

    if (linelen)
        nlines++;
    if (linelen > maxlength)
        maxlength = linelen;

    /* set up the result and the line buffer */

    result = (char **) malloc((nlines + 2) * sizeof(char *));
    if (!result)
        return NULL;
    
    buffer = (char *) malloc(maxlength + 2);
    if (!buffer)
        return NULL;

    /* now reprocess the file and store the lines */

    rewind(infile);
    nlines = 0;
    while (fgets(buffer, maxlength + 1, infile) != NULL)
    {
        result[nlines] = strdup(buffer);
        if (!result[nlines])
            return NULL;
        nlines++;
    }

    fclose(infile);
    result[nlines] = NULL;

    return result;
}

/*
 * write an array of lines to a file
 */
static int
writefile(char *path, char **lines)
{
    FILE        *out_file;
    char        **line;

    if ((out_file = fopen(path, "w")) == NULL)
        return 1;
    for (line = lines; *line != NULL; line++)
    {
        if (fputs(*line, out_file) < 0)
            return 1;
        free(*line);
    }
    if (fclose(out_file))
        return 1;
    
    return 0;
}

/*
 * End of initdb lifted code!
*/

static char **
append_line(char **lines, char *newline)
{
    char **ret;
    char **line;
    int linecount = 0;

    for (line = lines; *line != NULL; line++)
        linecount++;
    
    ret = realloc(lines, sizeof(char *)*(linecount+2));
    if (!ret)
        return NULL;

    lines[linecount] = strdup(newline);
    lines[linecount+1] = NULL;
    return lines;
}

/*
 * Main entry point for DLL 
 */

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    return TRUE;
}

/* Just get the last error as a system formatted string */
WCHAR *lasterror_string()
{
    int e = GetLastError();
    WCHAR *newstr = NULL;

    if (!FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&newstr, 10, NULL))
    {
        newstr = LocalAlloc(0, 128);
        swprintf(newstr,128,__("Unknown error %i"), e);
    }
    return newstr;
}

/*
 * Show a message box, optionally formatted, always in foreground
 */
int MsgBox(int param, WCHAR *title, WCHAR *fmt, ...)
{
    static WCHAR tmpfmt[2048];
    static WCHAR tmpmsg[2048];
    int i,j;
    va_list ap;

    /* Handle %m manually here, since vsnprintf won't do it for us  for 
     * win32 error codes. */
    for (i=0,j=0; fmt[i]; i++,j++)
    {
        if (fmt[i] == '%' &&
            fmt[i+1] == 'm')
        {
            WCHAR *s = lasterror_string();
            i++; /* Skip past the 'm' as well */
            wcscpy(&tmpfmt[j],s);
            j += wcslen(s) - 1;
            LocalFree(s);
        }
        else
            tmpfmt[j] = fmt[i];
    }
    tmpfmt[j]=0;

    va_start(ap, fmt);
    tmpmsg[sizeof(tmpmsg)-1] = 0;
    _vsnwprintf(tmpmsg, sizeof(tmpmsg)-1, tmpfmt, ap);
    va_end(ap);

    return MessageBoxW(0, tmpmsg, title?title:__("Internal error"), (param?param:MB_ICONERROR) | MB_SETFOREGROUND);
}


/*
 * Use MSI calls to send back an error message 
 */
static void SendErrorMessage(MSIHANDLE hInstall, WCHAR *fmt, ...)
{
    MSIHANDLE hRecord;
    static WCHAR tmpfmt[2048];
    static WCHAR tmpmsg[2048];
    int i,j;
    va_list ap;

    for (i=0,j=0; fmt[i]; i++,j++)
    {
        if (fmt[i] == '%' &&
            fmt[i+1] == 'm')
        {
            WCHAR *s = lasterror_string();
            i++;
            wcscpy(&tmpfmt[j],s);
            j += wcslen(s) -1;
            LocalFree(s);
        }
        else
            tmpfmt[j] = fmt[i];
    }
    tmpfmt[j]=0;

    va_start(ap, fmt);
    tmpmsg[sizeof(tmpmsg)-1] = 0;
    _vsnwprintf(tmpmsg, sizeof(tmpmsg)-1, tmpfmt, ap);
    va_end(ap);

    hRecord = MsiCreateRecord(0);
    if (hRecord == 0)
    {
        MsgBox(0, NULL, __("Failed to create Windows Installer database record."));
        return;
    }
    if (MsiRecordSetStringW(hRecord, 0, tmpmsg) != ERROR_SUCCESS)
    {
        MsgBox(0, NULL, L"Failed to set a string in the Windows Installer database.");
        return;
    }
    MsiProcessMessage(hInstall,INSTALLMESSAGE_ERROR, hRecord);
    MsiCloseHandle(hRecord);
}

/* Check filesystem type is NTFS */
static int is_ntfs(char *path)
{
    TCHAR fstype[MAX_PATH];
    char root[4];
    
    _snprintf(root, 3, "%s", path);
    
    if (root[1] != ':')
        return 0;
    
    if (root[2] != '\\')
        root[2] = '\\';
    
    root[3] = 0;
    
    if (!GetVolumeInformation(root, NULL, 0, NULL, NULL, NULL, fstype, MAX_PATH))
        return 0;
    
    if (strcmp(fstype, "NTFS") == 0)
        return 1;
    
    return 0;
}

/* easy way to get a file size */
long FileLength(char *filename)
{
    struct stat info;

    if(stat(filename, &info)!=0)
        info.st_size=-1;

    return(info.st_size);
}


/*
 * Check if this is a domain controller. If so, change the default value
 * of the SERVICEDOMAIN property, if it was previously blank.
 */
static UINT CheckDC(MSIHANDLE hInstall)
{
    OSVERSIONINFOEX verinfo;
    char prevdom[128];
    DWORD sz;

    ZeroMemory(&verinfo,sizeof(verinfo));
    verinfo.dwOSVersionInfoSize = sizeof(verinfo);

    sz = sizeof(prevdom);
    ZeroMemory(prevdom,sizeof(prevdom));
    if (MsiGetPropertyA(hInstall, "SERVICEDOMAIN", prevdom, &sz) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to get SERVICEDOMAIN property!");
        return ERROR_INSTALL_FAILURE;
    }

    /* If domain is other than ".", then the user changed it on the commandline,
       so we leave it alone */
    if (strcmp(prevdom,"."))
        return ERROR_SUCCESS;

    if (!GetVersionEx((OSVERSIONINFO*)&verinfo))
    {
        MsgBox(0,NULL,L"Failed to retrieve the operating system version.");
        return ERROR_INSTALL_FAILURE;
    }
    if (verinfo.wProductType == VER_NT_DOMAIN_CONTROLLER)
    {
        /* Machine is a domain controller. So get the domain and set as dflt */
        WKSTA_INFO_100 *wkinfo = NULL;
        NET_API_STATUS status;
        char domainname[256];

        status = NetWkstaGetInfo(NULL, 100, (LPBYTE*)&wkinfo);
        if (status != NERR_Success)
        {
            MsgBox(0,NULL,L"Failed to retrieve workstation information.");
            return ERROR_INSTALL_FAILURE;
        }

        _snprintf(domainname, sizeof(domainname), "%S", wkinfo->wki100_langroup);
        if (MsiSetPropertyA(hInstall, "SERVICEDOMAIN", domainname) != ERROR_SUCCESS)
        {
            MsgBox(0,NULL,L"Failed to set the 'SERVICEDOMAIN' property!");
            return ERROR_INSTALL_FAILURE;
        }

        if (wkinfo != NULL)
            NetApiBufferFree(wkinfo);
    }
    else
    {
        /* Not a domain controller - default to computer name */
        char tmpname[256];
        DWORD sz = sizeof(tmpname)-1;
        ZeroMemory(tmpname,sizeof(tmpname));
        if (!GetComputerName(tmpname, &sz))
        {
            MsgBox(0,NULL,L"Failed to get the local computer name.");
            return ERROR_INSTALL_FAILURE;
        }
        if (MsiSetPropertyA(hInstall, "SERVICEDOMAIN", tmpname) != ERROR_SUCCESS)
        {
            MsgBox(0,NULL,L"Failed to set the 'SERVICEDOMAIN' property.");
            return ERROR_INSTALL_FAILURE;
        }

    }
    return ERROR_SUCCESS;
}


static UINT CheckOneDll(MSIHANDLE hInstall, char *dllname, char *propertyname)
{
    HANDLE hLib;

    hLib = LoadLibraryEx(dllname, NULL, 0);
    if (hLib == NULL)
    {
        if (MsiSetPropertyA(hInstall, propertyname, "0") != ERROR_SUCCESS)
            return ERROR_INSTALL_FAILURE;
    }
    else
    {
        FreeLibrary(hLib);
        if (MsiSetPropertyA(hInstall, propertyname, "1") != ERROR_SUCCESS)
            return ERROR_INSTALL_FAILURE;
    }
    return ERROR_SUCCESS;
}

static int
FileExists(char *filename)
{
    DWORD attr;

    attr = GetFileAttributes(filename);
    if (attr == 0xFFFFFFFF)
        return 0;
    if (attr & FILE_ATTRIBUTE_REPARSE_POINT ||
        attr & FILE_ATTRIBUTE_DIRECTORY)
        return 0;
    return 1;
}

/*
 * Check if required DLLs are loadable
 */
__declspec(dllexport)
UINT __stdcall CheckAvailableDLLs(MSIHANDLE hInstall)
{
    char *env;
    int skip = 0;
 #define ONEDLL(dll,prop) {UINT r = CheckOneDll(hInstall,dll,prop);if (r != ERROR_SUCCESS) return r;}

    pginst_settext(hInstall);

    env = getenv("PATH");
    if (env != NULL)
    {
        if (strstr(env,"cygwin\\bin"))
        {
            MsgBox(0, __("Cygwin installed"), __("Your system PATH variable contains 'cygwin\\bin'. Due to bugs in several versions of some DLL files found in the cygwin directories, all external procedural langauges have been disabled. You are strongly recommended to remove the cygwin directories from the system PATH and restart the installation."));
            skip = 1;
        }
    }

    if (FileExists("c:\\program"))
    {
        MsgBox(0, __("File c:\\program found"), __("Your system contains a file called 'c:\\program'. If this file exists, the PostgreSQL service will not start if installed under 'c:\\program files'. Either remove this file, or install PostgreSQL in a different location."));
    }

    if (!skip) {
        ONEDLL("perl510.dll","HAS_PERL");
        ONEDLL("tcl85.dll","HAS_TCL");
        ONEDLL("python26.dll","HAS_PYTHON");
        ONEDLL("jvm.dll","HAS_JAVA");
    }

    {UINT r = CheckDC(hInstall); if (r != ERROR_SUCCESS) return r;}

    return ERROR_SUCCESS;
}

/*
 * Ensure that we are safe from the "terminal server bug"
 */
__declspec(dllexport)
UINT __stdcall ValidateSession(MSIHANDLE hInstall)
{
    WCHAR *tsmsg;
    OSVERSIONINFO ovi;
    int r;
    char tmpbuf[128];
    DWORD szbuf = sizeof(tmpbuf);

    pginst_settext(hInstall);

    tsmsg = __("The PostgreSQL installer must be run on the system console, not in a terminal services session.");

    ovi.dwOSVersionInfoSize = sizeof(ovi);

    if (!GetVersionEx(&ovi))
    {
        MsgBox(0,NULL,L"Failed to retrieve the operating system version.");
        return ERROR_INSTALL_FAILURE;
    }

    /* If SKIPTSCHECK is set to 1, skip the entire terminal services check.
       If we can't read the propery at all, treat it as not present. */
    ZeroMemory(tmpbuf,sizeof(tmpbuf));
    if (MsiGetPropertyA(hInstall, "SKIPTSCHECK", tmpbuf, &szbuf) == ERROR_SUCCESS)
    {
        if (!strcmp(tmpbuf, "1"))
            return ERROR_SUCCESS;
    }


    /* There used to be special code for NT4 here. But since we don't support
     * initdb'ing on NT4 anyway, there is no need to care about it, as this
     * issue only affects the initdb step.
     */
    if (ovi.dwMajorVersion < 5)
        return ERROR_SUCCESS;

    /*
     * Terminal Server bug doesn't appear to affect Windows Server 2003.
     * That makes this check a lot easier
     */
    if (ovi.dwMajorVersion > 5 ||       /* "longhorn" server? */
        ovi.dwMinorVersion > 0)          /* Windows XP = 5.1, Windows 2003 = 5.2, both unaffected */
        return ERROR_SUCCESS;

    /* SM_REMOTESESSION returns TRUE when in a terminal server session. Does
     * not return TRUE for a console RDP session. */
    SetLastError(0);
    r = GetSystemMetrics(SM_REMOTESESSION);
    if (GetLastError() != 0)
    {
        if (GetLastError() == ERROR_MOD_NOT_FOUND)
            /* Module not found should mean there is no terminal services
             * support at all, so assume we are safe */
            return ERROR_SUCCESS;

        MsgBox(0,NULL,L"Failed to get system metrics for terminal services: %i", (int)GetLastError());
        return ERROR_INSTALL_FAILURE;
    }
    if (r)
    {
        MsgBox(0,NULL,tsmsg);
        return ERROR_INSTALL_FAILURE;
    }

    return ERROR_SUCCESS;
}

/* Verify that an account can be logged in to */
static BOOL TestLogin(char *domain, char *account, char *password, WCHAR *errmsg, DWORD errsize)
{
    HANDLE token;
    if (!LogonUser(account,
                   domain,
                   password,
                   LOGON32_LOGON_SERVICE,
                   LOGON32_PROVIDER_DEFAULT,
                   &token))
    {
        _snwprintf(errmsg,errsize,__("Invalid username specified: %s"),lasterror_string());
        CloseHandle(token);
        return FALSE;
    }
    CloseHandle(token);

    return TRUE;
}

/*
 * Validate service account information
 */
__declspec(dllexport)
UINT __stdcall ValidateService(MSIHANDLE hInstall)
{
    char account[256];
    char domain[256];
    char machine[256];
    char password[256];
    char password2[256];
	char tmpbuf[256];
    WCHAR errbuf[156];
    DWORD szbuf;
	FILE *f;

    pginst_settext(hInstall);

    ZeroMemory(account,sizeof(account));
    ZeroMemory(domain,sizeof(domain));
    ZeroMemory(machine,sizeof(machine));
    ZeroMemory(password,sizeof(password));
    ZeroMemory(password2,sizeof(password2));
    ZeroMemory(errbuf,sizeof(errbuf));
    ZeroMemory(tmpbuf,sizeof(tmpbuf));

    if (MsiSetPropertyA(hInstall, "SERVICEOK", "0") != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to set the 'SERVICEOK' property.");
        return ERROR_INSTALL_FAILURE;
    }

    szbuf = sizeof(account);
    if (MsiGetPropertyA(hInstall,"SERVICEACCOUNT", account, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the 'SERVICEACCOUNT' property.");
        return ERROR_SUCCESS;
    }

    szbuf = sizeof(domain);
    if (MsiGetPropertyA(hInstall,"SERVICEDOMAIN", domain, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the 'SERVICEDOMAIN' property.");
        return ERROR_SUCCESS;
    }

    szbuf = sizeof(password);
    if (MsiGetPropertyA(hInstall,"SERVICEPASSWORD", password, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the service account password.");
        return ERROR_SUCCESS;
    }

    szbuf = sizeof(password2);
    if (MsiGetPropertyA(hInstall,"SERVICEPASSWORDV", password2, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the service account password verification.");
        return ERROR_SUCCESS;
    }

    if (strcmp(password, password2))
    {
        MsgBox(0,__("Invalid password"),__("The passwords entered do not match!"));
        return ERROR_SUCCESS;
    }

    if (strchr(password,';') || strchr(password,'"'))
    {
        MsgBox(0,__("Invalid password"),__("Passwords may not contain quotes or semicolons."));
        return ERROR_SUCCESS;
    }

    if (!strlen(account))
    {
        MsgBox(0,__("Missing field"),__("You must specify an account name to run the service under."));
        return ERROR_SUCCESS;
    }

    if (!strlen(domain) || !strcmp(domain,"."))
    {
        DWORD sz = sizeof(domain)-1;
        if (!GetComputerName(domain, &sz))
        {
            MsgBox(0,NULL,L"Failed to get the local computer name.");
            return ERROR_SUCCESS;
        }
    }

    /* Check if the user exists */
    if (!CheckUserExists(domain, account))
    {
        if (MsgBox(MB_YESNO | MB_ICONQUESTION,__("Account error"),__("User '%S\\%S' was not found. Would you like the account to be created for you?"), domain, account) == IDNO)
          return ERROR_SUCCESS;

        if (MsiSetPropertyA(hInstall,"CREATESERVICEUSER", "1") != ERROR_SUCCESS)
        {
            MsgBox(0,NULL,L"Failed to write 'CREATESERVICEUSER' property.");
            return ERROR_INSTALL_FAILURE;
        }

        if (MsiSetPropertyA(hInstall,"SERVICEPASSWORD", password) != ERROR_SUCCESS ||
            MsiSetPropertyA(hInstall,"SERVICEPASSWORDV", password) != ERROR_SUCCESS)
        {
            MsgBox(0,NULL,L"Failed to write the 'SERVICEPASSWORD' property.");
            return ERROR_INSTALL_FAILURE;
        }

        if (MsiSetPropertyA(hInstall, "SERVICEOK", "1") != ERROR_SUCCESS)
        {
            MsgBox(0,NULL,L"Failed to set the 'SERVICEOK' property!");
            return ERROR_INSTALL_FAILURE;
        }
        return ERROR_SUCCESS;
    }

    /* Check privileges */
    if (!CheckUserPrivileges(domain, account, FALSE, errbuf, sizeof(errbuf)/sizeof(WCHAR)) &&
           wcslen(errbuf))
    {
        MsgBox(0, NULL, errbuf);
        return ERROR_SUCCESS;
    }

    /* If account already existed, verify that we can use it */
    if (!TestLogin(domain, account, password, errbuf, sizeof(errbuf)/sizeof(WCHAR)))
    {
        MsgBox(0,__("Account error"),errbuf);
        return ERROR_SUCCESS;
    }
    
    /*
     * Set the service domain to be written to the registry.
     * This is required on Vista on which a machine name in the service user
     * account will cause the SCM to try to start the Netlogon service as a
     * dependency. This will fail if the machine is not on a domain because
     * netlogon knows it's not needed and quits, causing the postgres service
     * to quit as well. We use the machine name elsewhere in the installer 
     * because it's far more user friendly.
     */
    szbuf = sizeof(machine)-1;
    if (!GetComputerName(machine, &szbuf))
    {
        MsgBox(0,NULL,L"Failed to get the local computer name.");
        return ERROR_SUCCESS;
    }
    
    if (!_stricmp(machine, domain))
    {
        if (MsiSetPropertyA(hInstall, "SCMSERVICEDOMAIN", ".") != ERROR_SUCCESS)
        {
            MsgBox(0,NULL,L"Failed to set the 'SCMSERVICEDOMAIN' property.");
            return ERROR_INSTALL_FAILURE;
        }
    }
    else
    {
        if (MsiSetPropertyA(hInstall, "SCMSERVICEDOMAIN", domain) != ERROR_SUCCESS)
        {
            MsgBox(0,NULL,L"Failed to set the 'SCMSERVICEDOMAIN' property.");
            return ERROR_INSTALL_FAILURE;
        }
    }


    if (MsiSetPropertyA(hInstall, "SERVICEOK", "1") != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to set the 'SERVICEOK' property!");
        return ERROR_INSTALL_FAILURE;
    }

	/* Check if there is an existing data directory, with the same version. If it's there, turn off initdb */
    ZeroMemory(tmpbuf,sizeof(tmpbuf));
    szbuf = sizeof(tmpbuf)-1;
    if (MsiGetPropertyA(hInstall, "DATADIR", tmpbuf, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the 'DATADIR' property.");
        return ERROR_SUCCESS;
    }
	strcat(tmpbuf,"\\global\\pg_control");
	f = fopen(tmpbuf,"rb");
	if (f)
	{
		int r = CheckPgControl(f);
		/* Always disable initdb when pg_control exists */
		MsiSetPropertyA(hInstall, "DOINITDB", 0);

		/* Show a message to the user */
		if (r)
			MsgBox(0,__("Existing data directory"),__("Data directory contains a PostgreSQL database of a different version then the one you are installing. You must either remove this data directory, or install into a different one, and then initdb. Proceeding to install without initdb will leave a database that cannot start."));
		else
			MsgBox(MB_ICONINFORMATION,__("Existing data directory"),__("Data directory already contains a PostgreSQL database of this version. This database will automatically be used, there is no need to initdb. If you want a clean database, remove the existing data directory and try again."));
	}

    return ERROR_SUCCESS;
}

/*
 * Check that the RunAs service is started
 */
static BOOL CheckRunAsService(MSIHANDLE hInstall)
{
    char * servicename="Secondary Logon";
    SC_HANDLE schandle;
    SC_HANDLE servicehandle;
    SERVICE_STATUS status;

    schandle = OpenSCManager(NULL, NULL, GENERIC_READ);
    if (schandle == NULL)
    {
        MsgBox(0,__("Service check"),__("Failed to open service control manager: %m"));
        return FALSE;
    }

    servicehandle = OpenService(schandle, "seclogon", GENERIC_READ);
    if (servicehandle == NULL)
    {
        MsgBox(0,__("Service check"),__("Failed to open the %S service: %m"),servicename);
        CloseServiceHandle(schandle);
        return FALSE;
    }

    ZeroMemory(&status,sizeof(status));
    if (!QueryServiceStatus(servicehandle, &status))
    {
        MsgBox(0,__("Service check"),__("Failed to query status of the %S service: %m"),servicename);
        CloseServiceHandle(servicehandle);
        CloseServiceHandle(schandle);
        return FALSE;
    }

    CloseServiceHandle(servicehandle);
    CloseServiceHandle(schandle);

    if (status.dwCurrentState != SERVICE_RUNNING)
    {
        MsgBox(0,__("Service check"),__("The '%S' service is not running. This service is required for the installer to initialize the database.\nPlease start this service and try again."),servicename);
        return FALSE;
    }
    return TRUE;
}

/*
 * Validate input from the initdb dialog.
 */
__declspec(dllexport)
UINT __stdcall ValidateInitdb(MSIHANDLE hInstall)
{
    char tmpbuf[256];
    char tmpbuf2[256];
    char locale[256];
    char encoding[256];
    char *enccode;
    char *savelocale;
    char *testlocale;
    char *chr;
    DWORD szbuf = sizeof(tmpbuf)-1;
    WSADATA wsaData;
    struct sockaddr_in service;
    SOCKET ListenSocket;
    int dd, res, pos;
    long port;

    pginst_settext(hInstall);

    MsiSetPropertyA(hInstall, "INITDBOK", "0");

    ZeroMemory(tmpbuf,sizeof(tmpbuf));
    if (MsiGetPropertyA(hInstall, "DOINITDB", tmpbuf, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the 'DOINITDB' property.");
        return ERROR_SUCCESS;
    }
    if (strcmp(tmpbuf,"1"))
    {
        /* Initdb checkbox *not* checked. */

        ZeroMemory(tmpbuf,sizeof(tmpbuf));
        szbuf = sizeof(tmpbuf)-1;
        if (MsiGetPropertyA(hInstall, "DATADIR", tmpbuf, &szbuf) != ERROR_SUCCESS)
        {
            MsgBox(0,NULL,L"Failed to retrieve the 'DATADIR' property.");
            return ERROR_SUCCESS;
        }
        dd = check_data_dir(tmpbuf);
        if (dd == -1)
        {
            MsgBox(0,__("Data directory error"),__("Unable to access the specified data directory."));
            return ERROR_SUCCESS;
        }
        if (dd == 0 || dd == 1)
        {
            if (MsgBox(MB_YESNO | MB_ICONQUESTION,__("Initdb warning"),__("The specified data directory does not contain an existing database cluster. Installing without initializing a database cluster in this case will produce an installation that will not start.\n\nAre you sure you want to continue without running initdb?")) != IDYES)
                return ERROR_SUCCESS;
        }

        /* Datadir exists and we don't initdb, or the user really wants it,
         * so permit going on without initdb */
        MsiSetPropertyA(hInstall, "INITDBOK", "1");
        return ERROR_SUCCESS;
    }

    /* Initdb checkbox is checked - verify fields */

    /* Port */
    ZeroMemory(tmpbuf,sizeof(tmpbuf));
    szbuf = sizeof(tmpbuf)-1;
    if (MsiGetPropertyA(hInstall, "LISTENPORT", tmpbuf, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the 'LISTENPORT' property.");
        return ERROR_SUCCESS;
    }
    chr = tmpbuf;
    if (strlen(tmpbuf) < 1 || strlen(tmpbuf) > 5)
    {
        MsgBox(0,__("Invalid port"),__("The port number entered is not valid."));
        return ERROR_SUCCESS;
    }
    for ( ; *chr != '\0'; chr++ )
    {
        if (*chr < '0' || *chr > '9')
        {
            MsgBox(0,__("Invalid port"),__("The port number entered is not valid."));
            return ERROR_SUCCESS;
        }
    }
    port = atol(tmpbuf);
    if (port < 1 || port > 65535)
    {
        MsgBox(0,__("Invalid port"),__("The port number entered is not valid."));
        return ERROR_SUCCESS;
    }

    /*
     * Check to see if we can bind to the specified port.
     * This is important because otherwise we might end up running scripts against
     * an existing installation (as happened to Dave a few times during testing :-p
     * Of course, why the installation didn't fail when service startup obviously went
     * wrong is another matter...
     */

    res = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (res != NO_ERROR)
    {
        MsgBox(0, __("Network error"), __("The network subsystem could not be started."));
        return ERROR_INSTALL_FAILURE;
    }

    /* Create a socket */
    ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ListenSocket == INVALID_SOCKET)
    {
        MsgBox(0, __("Network error"), __("Error creating network socket: %d."),WSAGetLastError());
        WSACleanup();
        return ERROR_INSTALL_FAILURE;
    }

    /* We're only interested in localhost at installation... */
    service.sin_family = AF_INET;
        service.sin_addr.s_addr = inet_addr("127.0.0.1");
        service.sin_port = htons(port);

    /* Now bind to the socket */
    if (bind(ListenSocket, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) {
        if (WSAGetLastError() == WSAEADDRINUSE)
        {
            MsgBox(0,__("Invalid port"),__("The port number entered is already in use. Please specify a different port."));
        }
        else
        {
            MsgBox(0, __("Network error"), __("Error binding the test network socket: %d."), WSAGetLastError());
        }
        closesocket(ListenSocket);
        WSACleanup();
        return ERROR_SUCCESS;
    }

    /* Tidy up the mess */
    closesocket(ListenSocket);
    WSACleanup();

    /* Locale/encoding */
    ZeroMemory(locale,sizeof(locale));
    ZeroMemory(encoding,sizeof(encoding));

    szbuf = sizeof(locale);
    if (MsiGetPropertyA(hInstall,"LOCALE", locale, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the 'LOCALE' property.");
        return ERROR_SUCCESS;
    }
    if (strcmp(locale, "DEFAULT")  == 0)
        _snprintf(locale, sizeof(locale)-1, "C");

    szbuf = sizeof(encoding);
    if (MsiGetPropertyA(hInstall,"ENCODING", encoding, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the 'ENCODING' property.");
        return ERROR_SUCCESS;
    }
    if (strcmp(encoding, "DEFAULT")  == 0)
        _snprintf(encoding, sizeof(encoding)-1, "SQL_ASCII");

    /* Check the locale */
    if (setlocale(LC_COLLATE, locale) == NULL)
    {
        MsgBox(0,__("Invalid locale"),__("The selected locale was not recognised by system."));
        return ERROR_SUCCESS;
    }


    /*
     * Check the encoding/locale combination will be accepted by initdb.
     * SQL_ASCII/UTF8 should always be accepted as an encoding, and the C
     * locale can use any encoding. Others should be match what setlocale()
     * defaults to with no encoding qualifier.
     */
    if (strcmp(encoding, "SQL_ASCII") != 0 &&
        strcmp(encoding, "UTF8") != 0 &&
        strcmp(locale, "C") != 0)
    {
        /* Find the encoding number */
        for (pos=0; pos<num_database_encodings; pos++)
        {
            if (strcmp(database_encodings[pos].name, encoding) == 0)
            {
                enccode = database_encodings[pos].windows_code;
                continue;
            }
        }

        ZeroMemory(tmpbuf,sizeof(tmpbuf));
        _snprintf(tmpbuf, sizeof(tmpbuf)-1, "%s.%s", locale, enccode);

        /* Test the encoding/locale */
        savelocale = setlocale(LC_CTYPE, NULL);
 		savelocale = strdup(savelocale); //to fix the bug as identified in pg_locale.c
 		if(savelocale == NULL) {
 			MsgBox(0,__("Out of memory"),__("Out of memory"));
            return ERROR_SUCCESS;
 		}

        testlocale = setlocale(LC_CTYPE, locale);
 		testlocale = strdup(testlocale); //to fix the bug as identified in pg_locale.c
 		if(testlocale == NULL) {
 			MsgBox(0,__("Out of memory"),__("Out of memory"));
            return ERROR_SUCCESS;
 		}

        setlocale(LC_CTYPE, savelocale);
        if (strcmp(testlocale, tmpbuf) != 0)
        {
            MsgBox(0,__("Locale/encoding mismatch"),__("The selected encoding is not valid for the chosen locale."));
            return ERROR_SUCCESS;
        }
    }

    /* Password */
    ZeroMemory(tmpbuf,sizeof(tmpbuf));
    szbuf = sizeof(tmpbuf)-1;
    if (MsiGetPropertyA(hInstall, "SUPERPASSWORD", tmpbuf, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the 'SUPERPASSWORD' property.");
        return ERROR_SUCCESS;
    }
    szbuf = sizeof(tmpbuf2)-1;
    if (MsiGetPropertyA(hInstall, "SUPERPASSWORDV", tmpbuf2, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to retrieve the 'SUPERPASSWORDV' property.");
        return ERROR_SUCCESS;
    }
    if (strcmp(tmpbuf, tmpbuf2))
    {
        MsgBox(0,__("Invalid password"),__("The passwords entered do not match!"));
        return ERROR_SUCCESS;
    }
    if (strlen(tmpbuf)<4) 
    {
        MsgBox(0,__("Invalid password"),__("You must enter a password of at least 4 characters in length."));
        return ERROR_SUCCESS;
    }
    if (strchr(tmpbuf,';') || strchr(tmpbuf,'"') || strchr(tmpbuf, '\\')) 
    {
        MsgBox(0,__("Invalid password"),__("Passwords may not contain quotes, semicolons or backslashes."));
        return ERROR_SUCCESS;
    }

    /* Data dir */
    ZeroMemory(tmpbuf,sizeof(tmpbuf));
    szbuf = sizeof(tmpbuf)-1;
    if (MsiGetPropertyA(hInstall, "DATADIR", tmpbuf, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to the 'DATADIR' property.");
        return ERROR_SUCCESS;
    }
    dd = check_data_dir(tmpbuf);
    if (dd == 2)
    {
        MsgBox(0,__("Data directory error"),__("The specified data directory is not empty.\n\nIf you have an existing database with the same major version number, you do not need to initialize a database cluster.\nIf you have an existing database with a different major version you need to backup your old database and create a new one."));
        return ERROR_SUCCESS;
    }
    if (dd != 0 && dd != 1)
    {
        MsgBox(0,__("Data directory error"),__("Unable to access the specified data directory."));
        return ERROR_SUCCESS;
    }
    if (!is_ntfs(tmpbuf))
    {
        MsgBox(0,__("Data directory error"),__("The PostgreSQL data directory must be on an NTFS formatted volume.\n\nIf you wish to install the data directory on another type of partition (which is not recommended, and is unsupported) you must initialize the database cluster manually by running initdb.exe."));
        return ERROR_SUCCESS;
    }

    /* Warn the user to edit pg_hba.conf */
    ZeroMemory(tmpbuf,sizeof(tmpbuf));
    szbuf = sizeof(tmpbuf)-1;
    if (MsiGetPropertyA(hInstall, "PERMITREMOTE", tmpbuf, &szbuf) != ERROR_SUCCESS)
    {
        MsgBox(0,NULL,L"Failed to the 'PERMITREMOTE' property.");
        return ERROR_SUCCESS;
    }
    if (!strcmp(tmpbuf, "1"))
        MsgBox(MB_ICONINFORMATION,__("Remote connections"), __("You have opted for the server to listen for connections on all local addresses, not just 'localhost'.\n\n In order for clients to connect successfully, you must also grant access to specific host addresses or networks by editing the pg_hba.conf file in the data directory and restarting the PostgreSQL service."));
    
    /* RunAs Service */
    if (!CheckRunAsService(hInstall))
        return ERROR_SUCCESS;

    /* Otherwise, success */
    MsiSetPropertyA(hInstall, "INITDBOK", "1");
    return ERROR_SUCCESS;
}


static void CleanupFiles(char *serverdir)
{
    char filename[1024];

    sprintf(filename,"%s\\tmp\\initdb.log",serverdir);
    unlink(filename);
    /* let's lose the temp dir as well.. */
    sprintf(filename,"%s\\tmp",serverdir);
    rmdir(filename);
    
}

/*
 * Set permissions on the directories installed.
 * This is done by calling the pgperm.bat file
 */
__declspec(dllexport)
UINT __stdcall SetPermissions(MSIHANDLE hInstall)
{
    char mybuf[8192];
    char cmdline[8192];
    char *langid,*serverdir,*datadir,*accountname,*accountdomain,*password;
    DWORD szbuf;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    DWORD exitcode;

    szbuf = sizeof(mybuf);
    ZeroMemory(mybuf,sizeof(mybuf));
    if (MsiGetPropertyA(hInstall, "CustomActionData", mybuf, &szbuf) != ERROR_SUCCESS)
    {
        SendErrorMessage(hInstall, L"Failed to retrieve the 'CustomActionData' property whilst preparing to set the installation permissions.");
        return ERROR_INSTALL_FAILURE;
    }

    langid = mybuf;

    serverdir = strchr(langid,';');
    if (!serverdir)
    {
        SendErrorMessage(hInstall,L"Malformed permissions property: 'langid'.");
        return ERROR_INSTALL_FAILURE;
    }
    *serverdir =0;
    serverdir++;

    pginst_settext2(langid);
    
    datadir = strchr(serverdir,';');
    if (!datadir)
    {
        SendErrorMessage(hInstall,L"Malformed permissions property: 'datadir'.");
        return ERROR_INSTALL_FAILURE;
    }
    *datadir=0;
    datadir++;

    if (serverdir[strlen(serverdir)-1] == '\\')
        serverdir[strlen(serverdir)-1]=0;

    accountdomain = strchr(datadir,';');
    if (!accountdomain)
    {
        SendErrorMessage(hInstall,L"Malformed permissions property: 'accountdomain'.");
        return ERROR_INSTALL_FAILURE;
    }
    *accountdomain=0;
    accountdomain++;

    accountname = strchr(accountdomain,';');
    if (!accountname)
    {
        SendErrorMessage(hInstall,L"Malformed permissions property: 'accountname'.");
        return ERROR_INSTALL_FAILURE;
    }
    *accountname=0;
    accountname++;

    password = strchr(accountname,';');
    if (!password)
    {
        SendErrorMessage(hInstall,L"Malformed permissions property 'password'.");
        return ERROR_INSTALL_FAILURE;
    }
    *password=0;
    password++;

    if (datadir[strlen(datadir)-1] == '\\')
        datadir[strlen(datadir)-1]=0;
    
    ZeroMemory(cmdline,sizeof(cmdline));
    
    /* If this is not an NTFS drive, set the skipcacls flag on the command line. */
    if (is_ntfs(serverdir))
    {
        _snprintf(cmdline,sizeof(cmdline)-1,"/c \"title Setting file permissions...&&\"%s\\bin\\pgperm.bat\" \"%s\" \"%s\" \"%s%s%s\" 0\"",
                 serverdir, serverdir, datadir, (strcmp(accountdomain,".")?accountdomain:""),(strcmp(accountdomain,".")?"\\":""),accountname);
    }
    else
    {
        _snprintf(cmdline,sizeof(cmdline)-1,"/c \"title Setting file permissions...&&\"%s\\bin\\pgperm.bat\" \"%s\" \"%s\" \"%s%s%s\" 1\"",
                 serverdir, serverdir, datadir, (strcmp(accountdomain,".")?accountdomain:""),(strcmp(accountdomain,".")?"\\":""),accountname);
    }

    ZeroMemory(&si,sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;
    ZeroMemory(&pi,sizeof(pi));
    
    if (!CreateProcess("cmd.exe",
                       cmdline,
                       NULL,
                       NULL,
                       FALSE,
                       0,
                       NULL,
                       NULL,
                       &si,
                       &pi))
    {
        SendErrorMessage(hInstall,__("Failed to create a process to set permissions."));
        return ERROR_INSTALL_FAILURE;
    }
    CloseHandle(pi.hThread);

    WaitForSingleObject(pi.hProcess, INFINITE);

    if (!GetExitCodeProcess(pi.hProcess, &exitcode))
    {
        SendErrorMessage(hInstall,L"Failed to get the process exit code from the permissions process.");
        return ERROR_INSTALL_FAILURE;
    }

    CloseHandle(pi.hProcess);

    if (exitcode != 0)
    {
        SendErrorMessage(hInstall,__("Failed to set permissions on the installed files.\nPlease see the logfile in '%S\\tmp\\pgperm.log'."),serverdir);
        return ERROR_INSTALL_FAILURE;
    }
    
    /* all successful, so we can safely remove the logfile */
    CleanupFiles(serverdir);

    return ERROR_SUCCESS;
}


static int fixslashes(char *str, int maxsize)
{
    char *cp1,*cp2;
    char *newstr = malloc(maxsize);
    if (!newstr)
        return 0;

    for (cp1=str,cp2=newstr; *cp1 && cp2 < newstr+maxsize-1; cp1++,cp2++)
    {
        *cp2 = *cp1;
        if (*cp1 == '\\')
        {
            cp2++;
            *cp2 = '\\';
        }
    }
    *cp2 = 0;
    strcpy(str, newstr);
    free(newstr);
    return 1;
}

__declspec(dllexport)
UINT __stdcall RunInitdb(MSIHANDLE hInstall)
{
    char *langid, *serverdir, *datadir, *sharedir, *listenport, *permitremote, *locale, *encoding, *superuser, *superpassword, *serviceaccount, *servicedomain, *servicepassword, *pljava;
    char cmdline[8192];
    char mybuf[8192], pwdfile[1024], tmpdir[1024], batfile[1024], conffile[1024], repltok[100];
    char **conflines;
    WCHAR wserviceaccount[MAX_PATH+1],wservicedomain[MAX_PATH+1],wservicepassword[MAX_PATH+1];
    DWORD szbuf;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    DWORD exitcode;
    FILE *f;

    int i;
    char actual_locale[1024], actual_encoding[1024], full_locale[1024];
    char wincodepage[128];

    szbuf = sizeof(mybuf);
    ZeroMemory(mybuf,sizeof(mybuf));
    if (MsiGetPropertyA(hInstall, "CustomActionData", mybuf, &szbuf) != ERROR_SUCCESS)
    {
        SendErrorMessage(hInstall,L"Failed to retrieve the 'CustomActionData' property whilst preparing to execute initdb.");
        return ERROR_INSTALL_FAILURE;
    }

    langid = mybuf;

    serverdir = strchr(langid,';');
    if (!serverdir)
    {
        SendErrorMessage(hInstall,L"Malformed permissions property: 'langid'.");
        return ERROR_INSTALL_FAILURE;
    }
    *serverdir =0;
    serverdir++;

    pginst_settext2(langid);

    datadir = strchr(serverdir,';');
    if (!datadir)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'datadir'.");
        return ERROR_INSTALL_FAILURE;
    }
    *datadir=0;
    datadir++;

    sharedir = strchr(datadir,';');
    if (!sharedir)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'sharedir'.");
        return ERROR_INSTALL_FAILURE;
    }
    *sharedir=0;
    sharedir++;

    listenport = strchr(sharedir,';');
    if (!listenport)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'listenport'.");
        return ERROR_INSTALL_FAILURE;
    }
    *listenport=0;
    listenport++;

    permitremote = strchr(listenport,';');
    if (!permitremote)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'permitremote'.");
        return ERROR_INSTALL_FAILURE;
    }
    *permitremote=0;
    permitremote++;

    locale = strchr(permitremote,';');
    if (!locale)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'locale'.");
        return ERROR_INSTALL_FAILURE;
    }
    *locale=0;
    locale++;

    encoding = strchr(locale,';');
    if (!encoding)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'encoding'.");
        return ERROR_INSTALL_FAILURE;
    }
    *encoding=0;
    encoding++;

    superuser = strchr(encoding,';');
    if (!superuser)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'superuser'.");
        return ERROR_INSTALL_FAILURE;
    }
    *superuser=0;
    superuser++;

    superpassword = strchr(superuser,';');
    if (!superpassword)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property!: 'superpassword'.");
        return ERROR_INSTALL_FAILURE;
    }
    *superpassword=0;
    superpassword++;

    servicedomain = strchr(superpassword,';');
    if (!servicedomain)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'servicedomain'.");
        return ERROR_INSTALL_FAILURE;
    }
    *servicedomain=0;
    servicedomain++;

    serviceaccount = strchr(servicedomain,';');
    if (!serviceaccount)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'serviceaccount'.");
        return ERROR_INSTALL_FAILURE;
    }
    *serviceaccount = 0;
    serviceaccount++;

    servicepassword = strchr(serviceaccount,';');
    if (!servicepassword)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'servicepassword'.");
        return ERROR_INSTALL_FAILURE;
    }
    *servicepassword=0;
    servicepassword++;

    pljava = strchr(servicepassword,';');
    if (!pljava)
    {
        SendErrorMessage(hInstall,L"Malformed initdb property: 'pljava'.");
        return ERROR_INSTALL_FAILURE;
    }
    *pljava=0;
    pljava++;

    /* Reset the DEFAULT encoding/locale if required */
    if (strcmp(encoding, "DEFAULT")  == 0)
        _snprintf(actual_encoding, sizeof(actual_encoding)-1, "SQL_ASCII");
    else
        _snprintf(actual_encoding, sizeof(actual_encoding)-1, encoding);

    if (strcmp(locale, "DEFAULT")  == 0)
        _snprintf(actual_locale, sizeof(actual_locale)-1, "C");
    else
        _snprintf(actual_locale, sizeof(actual_locale)-1, locale);

    /*
      Check if selected database encoding is also a supported system code page.
      If yes, then use Language_Region.CODEPAGE as --locale value; otherwise
      use just Language_Region.
    */

    ZeroMemory(wincodepage, sizeof(wincodepage));
    ZeroMemory(full_locale, sizeof(full_locale));

    for (i=0; i<num_database_encodings; i++)
    {
        if (strcmp(database_encodings[i].name, actual_encoding) == 0 &&
            database_encodings[i].windows_code[0] != 0)
        {
            _snprintf(full_locale, sizeof(full_locale)-1, "%s.%s", actual_locale, database_encodings[i].windows_code);

            if (setlocale(LC_COLLATE, full_locale) != NULL)
            {
                _snprintf(wincodepage, sizeof(wincodepage)-1, ".%s", database_encodings[i].windows_code);
            }
            break;
        }
    }


    if (serverdir[strlen(serverdir)-1] == '\\')
        serverdir[strlen(serverdir)-1] = 0;
    if (datadir[strlen(datadir)-1] == '\\')
        datadir[strlen(datadir)-1]=0;

    sprintf(tmpdir,"%s\\tmp", serverdir);
    if (_mkdir(tmpdir) != 0)
    {
        SendErrorMessage(hInstall, __("Failed to create temporary directory."));
        return ERROR_INSTALL_FAILURE;
    }

    sprintf(batfile,"%s\\tmp\\initdb.bat", serverdir);
    f = fopen(batfile,"w");
    if (!f)
    {
        SendErrorMessage(hInstall, __("Failed to create temporary batch file."));
        return ERROR_INSTALL_FAILURE;
    }
    fprintf(f,"@echo off\n");
    fprintf(f,"title Initializing database cluster\n");
    fprintf(f,"echo.\necho.\necho.\necho.\necho.\necho.\necho.\necho.\necho.\n");
    fprintf(f,"echo                       Initializing the database cluster.\n");
    fprintf(f,"echo.\necho.\necho.\n");
    fprintf(f,"echo                        This may take a minute or two...\n");
    fprintf(f,"SET LC_MESSAGES=EN\n");
    fprintf(f,"\"%s\\bin\\initdb\" --locale=\"%s%s\" -E %s -U \"%s\" -A md5 --pwfile \"%s\\tmp\\initdb.pwd\" -D \"%s\" > \"%s\\tmp\\initdb.log\" 2>&1",serverdir,actual_locale,wincodepage,actual_encoding,superuser,serverdir,datadir,serverdir);
    fclose(f);

    ZeroMemory(cmdline,sizeof(cmdline));
    _snprintf(cmdline,sizeof(cmdline)/sizeof(cmdline[0])-1,"cmd /c \"%s\\tmp\\initdb.bat\"",serverdir);

    swprintf(wserviceaccount,sizeof(wserviceaccount)/sizeof(wserviceaccount[0]),L"%S",serviceaccount);
    if (strlen(servicedomain) && strcmp(servicedomain,"."))
        swprintf(wservicedomain,sizeof(wservicedomain)/sizeof(wservicedomain[0]),L"%S",servicedomain);
    else
        /* Either no domain or ".", and
           <= Win2000 needs computer name and not "." */
    {
        DWORD sz = sizeof(wservicedomain)/sizeof(wservicedomain[0]);
        if (!GetComputerNameW(wservicedomain, &sz))
        {
            SendErrorMessage(hInstall, L"Failed to get the local computer name.");
            return ERROR_INSTALL_FAILURE;
        }
    }
    swprintf(wservicepassword,sizeof(wservicepassword)/sizeof(wservicepassword[0]),L"%S",servicepassword);
    
    ZeroMemory(&si,sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;
    ZeroMemory(&pi,sizeof(pi));

    sprintf(pwdfile,"%s\\tmp\\initdb.pwd", serverdir);
    f = fopen(pwdfile,"w");
    if (!f)
    {
        SendErrorMessage(hInstall, __("Failed to create password file for initdb."));
        return ERROR_INSTALL_FAILURE;
    }
    fprintf(f,"%s\n",superpassword);
    fclose(f);

    if (!CreateProcess("cmd.exe",
                       cmdline,
                       NULL,
                       NULL,
                       FALSE,
                       0,
                       NULL,
                       NULL,
                       &si,
                       &pi))
    {
        SendErrorMessage(hInstall,__("Failed to create a process to run initdb."));
        return ERROR_INSTALL_FAILURE;
    }
    CloseHandle(pi.hThread);

    WaitForSingleObject(pi.hProcess, INFINITE);

    unlink(pwdfile); /* Make sure we get rid of this file early! */

    if (!GetExitCodeProcess(pi.hProcess, &exitcode))
    {
        SendErrorMessage(hInstall,L"Failed to get process exit code from the initdb process!");
        return ERROR_INSTALL_FAILURE;
    }

    CloseHandle(pi.hProcess);

    unlink(batfile);

    if (exitcode != 0)
    {
        SendErrorMessage(hInstall,__("Failed to run initdb: %i!\nPlease see the logfile in '%S\\tmp\\initdb.log'.\nNote! You must read/copy this logfile before you click OK, or it will be automatically removed."),exitcode,serverdir);
        CleanupFiles(serverdir);
        return ERROR_INSTALL_FAILURE;
    }

    /* Rewrite parts of postgresql.conf as required */

    sprintf(conffile, "%s\\postgresql.conf", datadir);

    /* open the backup file, modify it, then write the real file */
    conflines = readfile(conffile);
    if (!conflines)
    {
        SendErrorMessage(hInstall,__("Failed to read the configuration file: '%S\\postgresql.conf'."),datadir);
        CleanupFiles(serverdir);
        return ERROR_INSTALL_FAILURE;
    }

    /* Port number */
    _snprintf(repltok, sizeof(repltok), "port = %s", listenport);
    conflines = replace_token(conflines, "#port = 5432", repltok);
    if (!conflines)
    {
        SendErrorMessage(hInstall,L"Failed update the 'port' configuration option.");
        CleanupFiles(serverdir);
        return ERROR_INSTALL_FAILURE;
    }

    /* Permit remote connections */
    if (!strcmp(permitremote,"1"))
    {
        conflines = replace_token(conflines, "#listen_addresses = 'localhost'", "listen_addresses = '*'");
        if (!conflines)
        {
            SendErrorMessage(hInstall,L"Failed update the 'listen_addresses' configuration option.");
            CleanupFiles(serverdir);
            return ERROR_INSTALL_FAILURE;
        }
    }


    /* Log destination */
    _snprintf(repltok, sizeof(repltok), "log_destination = 'stderr'");
    conflines = replace_token(conflines, "#log_destination = 'stderr'", repltok);
    if (!conflines)
    {
        SendErrorMessage(hInstall,L"Failed update the 'log_destination' configuration option.");
        CleanupFiles(serverdir);
        return ERROR_INSTALL_FAILURE;
    }

    /* Redirect stderr */
    _snprintf(repltok, sizeof(repltok), "logging_collector = on");
    conflines = replace_token(conflines, "#logging_collector = off", repltok);
    if (!conflines)
    {
        SendErrorMessage(hInstall,L"Failed update the 'logging_collector' configuration option.");
        CleanupFiles(serverdir);
        return ERROR_INSTALL_FAILURE;
    }

    /* log_line_prefix */
    _snprintf(repltok, sizeof(repltok), "log_line_prefix = '%%t '");
    conflines = replace_token(conflines, "#log_line_prefix = ''", repltok);
    if (!conflines)
    {
        SendErrorMessage(hInstall,L"Failed update the 'log_line_prefix' configuration option.");
        CleanupFiles(serverdir);
        return ERROR_INSTALL_FAILURE;
    }

    /* PL/Java */
    if (pljava[0] == '1')
    {
        conflines = replace_token(conflines, "#custom_variable_classes = ''", "custom_variable_classes = 'pljava'");
        if (conflines)
        {
            char classpath[1024];

            sprintf(classpath,"pljava.classpath='%spljava\\pljava.jar'\n",sharedir);
            if (fixslashes(classpath, sizeof(classpath)))
                conflines = append_line(conflines, classpath);
            else
                conflines = NULL;
        }
        if (!conflines)
        {
            SendErrorMessage(hInstall,L"Failed to update the pljava configuration options.");
            CleanupFiles(serverdir);
            return ERROR_INSTALL_FAILURE;
        }
    }
    
    /* plDebugger */
    _snprintf(repltok, sizeof(repltok), "shared_preload_libraries = '$libdir/plugins/plugin_debugger.dll'");
    conflines = replace_token(conflines, "#shared_preload_libraries = ''", repltok);
    if (!conflines)
    {
        SendErrorMessage(hInstall,L"Failed update the 'shared_preload_libraries' configuration option.");
        CleanupFiles(serverdir);
        return ERROR_INSTALL_FAILURE;
    }

        /* And write the file :-) */
    if (writefile(conffile, conflines))
    {
        SendErrorMessage(hInstall,__("Failed to write the configuration file: '%S\\postgresql.conf'."),datadir);
        CleanupFiles(serverdir);
        return ERROR_INSTALL_FAILURE;
    }
    
    chmod(conffile, 0600);
    free(conflines);

    return ERROR_SUCCESS;
}

#define PL_COUNT 7
const struct {
    char *plname;
    int trusted;
    char *customsqlfile;
} PLINFO[PL_COUNT] = {
    {"plpgsql",1,NULL},
    {"plperl",1,NULL},
    {"plperlu",0,NULL},
    {"plpythonu",0,NULL},
    {"pltcl",1,NULL},
    {"pltclu",0,NULL},
    {"pljava",0,"pljava\\install.sql"}
};

static int install_procedural_language(PGconn *conn, int pl, MSIHANDLE hInstall)
{
    PGresult *res;
    int status;
    char querybuf[8192];

    ZeroMemory(querybuf,sizeof(querybuf));

    /* Now that Tom has hardwired the PL defs into PostgreSQL, */
    /* we don't need to worry about validator or handler functions :-) */
    sprintf(querybuf, "CREATE %s LANGUAGE \"%s\";",PLINFO[pl].trusted?"TRUSTED":"", PLINFO[pl].plname);

    res = PQexec(conn, querybuf);
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        SendErrorMessage(hInstall,__("Database command error: %S\n"),PQerrorMessage(conn));
        status = 0;
    }
    else
        status = 1;

    if (res)
        PQclear(res);
       
    return status;
}

__declspec(dllexport)
UINT __stdcall InstallPl(MSIHANDLE hInstall)
{
    char mybuf[8192];
    DWORD szbuf = sizeof(mybuf);
    char *langid, *superuser, *superpassword, *listenport, *sharedir, *plstr;
    int PL[PL_COUNT];
    int pl;
    char connstr[1024];
    PGconn *conn;

    szbuf = sizeof(mybuf);
    ZeroMemory(mybuf,sizeof(mybuf));
    if (MsiGetPropertyA(hInstall, "CustomActionData", mybuf, &szbuf) != ERROR_SUCCESS)
    {
        SendErrorMessage(hInstall,L"Failed to retrieve the 'CustomActionData' property when preparing to install PLs");
        return ERROR_INSTALL_FAILURE;
    }

    langid = mybuf;
    superuser = strchr(langid,';');
    if (!superuser)
    {
        SendErrorMessage(hInstall,L"Malformed plinfo property: 'superuser'.");
        return ERROR_INSTALL_FAILURE;
    }
    *superuser = 0;
    superuser++;

    pginst_settext2(langid);

    superpassword = strchr(superuser,';');
    if (!superpassword)
    {
        SendErrorMessage(hInstall,L"Malformed plinfo property: 'superpassword'.");
        return ERROR_INSTALL_FAILURE;
    }
    *superpassword = 0;
    superpassword++;

    listenport = strchr(superpassword,';');
    if (!listenport)
    {
        SendErrorMessage(hInstall,L"Malformed plinfo property: 'listenport'.");
        return ERROR_INSTALL_FAILURE;
    }
    *listenport=0;
    listenport++;

    sharedir = strchr(listenport,';');
    if (!sharedir)
    {
        SendErrorMessage(hInstall,L"Mailformed plinfo property: 'sharedir'.");
        return ERROR_INSTALL_FAILURE;
    }
    *sharedir = 0;
    sharedir++;
    
    plstr = strchr(sharedir,';');
    if (!plstr)
    {
        SendErrorMessage(hInstall,L"Malformed plinfo property: 'plstr'.");
        return ERROR_INSTALL_FAILURE;
    }
    *plstr = 0;
    plstr++;

    ZeroMemory(&PL,sizeof(PL));
    for (pl = 0; pl < PL_COUNT; pl++)
    {
        char *plnext = strchr(plstr,';');
        if (!plnext)
        {
            SendErrorMessage(hInstall,L"Malformed list of PLs!");
            return ERROR_INSTALL_FAILURE;
        }
        if (*plstr == '1')
            PL[pl]=1;
        plstr = plnext+1;
    }

    /* Ok, all info returned, now try to connect */
    sprintf(connstr,"hostaddr=127.0.0.1 port=%s dbname=template1 user='%s' password='%s'",listenport,superuser,superpassword);

    /* Try 10 connects with 1 second in between, to get around the
     * "database is starting up" error */
    for (pl = 0; pl < 10; pl++)
    {
        Sleep(1000);
        conn = PQconnectdb(connstr);
        if (PQstatus(conn) == CONNECTION_OK)
            break;
    }
    if (PQstatus(conn) != CONNECTION_OK)
    {
        SendErrorMessage(hInstall,__("Failed to connect to the database. Procedural languages files are installed, but are not activated in any databases."));
        SendErrorMessage(hInstall,L"%S",PQerrorMessage(conn));
        return ERROR_SUCCESS;
    }


    for (pl = 0; pl < PL_COUNT; pl++)
    {
        if (PL[pl]) 
        {
            if (PLINFO[pl].customsqlfile)
            {
                /* Execute custom SQL file instead of CREATE LANGUAGE command */
                if (!RunSqlScript(hInstall, conn, sharedir, PLINFO[pl].customsqlfile))
                    SendErrorMessage(hInstall,__("Failed to enable procedural language %S %S. The files are installed, but are not activated in any database."),PLINFO[pl].plname,PLINFO[pl].trusted?"":" (untrusted)");
            }
            else
            {
                if (!install_procedural_language(conn, pl, hInstall))
                {
                    SendErrorMessage(hInstall,__("Failed to enable procedural language %S %S. The files are installed, but are not activated in any database."),PLINFO[pl].plname,PLINFO[pl].trusted?"":" (untrusted)");
                }
            }
        }
    }

    PQfinish(conn);

    return ERROR_SUCCESS;
}

#define CONTRIB_COUNT 34
const struct {
    char *contribname;
    char *script;
} CONTRIBINFO[CONTRIB_COUNT] = {
    {"Adminpack","adminpack.sql"},
    {"B-Tree GIN","btree_gin.sql"},
    {"B-Tree GiST","btree_gist.sql"},
    {"Chkpass","chkpass.sql"},
    {"CIText","citext.sql"},
    {"Cube","cube.sql"},
    {"DBlink","dblink.sql"},
    {"Earth Distance","earthdistance.sql"},
    {"Fuzzy String Match","fuzzystrmatch.sql"},
    {"Hstore","hstore.sql"},
    {"Integer Aggregate","int_aggregate.sql"},
    {"Integer Array","_int.sql"},
    {"ISN","isn.sql"},
    {"Large Objects (lo)","lo.sql"},
    {"L-Tree","ltree.sql"},
    {"Page Inspect","pageinspect.sql"},
    {"Buffer Cache","pg_buffercache.sql"},
    {"Freespace Map","pg_freespacemap.sql"},
    {"Statement Stats","pg_stat_statements.sql"},
    {"Trigram Matching","pg_trgm.sql"},
    {"Cryptographic Functions","pgcrypto.sql"},
    {"Row Lock Functions","pgrowlock.sql"},
    {"PGStatTuple","pgstattuple.sql"},
    {"SEG","seg.sql"},
    {"SPI - AutoInc","autoinc.sql"},
    {"SPI - Insert Username","insert_username.sql"},
    {"SPI - ModDateTime","moddatetime.sql"},
    {"SPI - RefInt","refint.sql"},
    {"SPI - Time Travel","timetravel.sql"},
    {"SSL Info","sslinfo.sql"},
    {"Table Functions","tablefunc.sql"},
    {"uuid ossp","uuid-ossp.sql"},
    {"XML 2","pgxml.sql"},
    {"plDebugger","pldbgapi.sql"}
};


static int RunSqlScript(MSIHANDLE hInstall, PGconn *conn, char *path, char *script)
{
    PGresult *res;
    FILE *fileptr;
    char filename[8192], line[1024], *sql;
    long filelen;
    long r;

    sprintf(filename, "%s%s", path, script);
    fileptr = fopen(filename, "r");
    
    if (!fileptr)
    {
        SendErrorMessage(hInstall,__("Could not open SQL script file: %S"), filename);
        return 0;
    }
    
    /* Read the file */
    filelen = FileLength(filename);
    sql = malloc(filelen + 1);
    ZeroMemory(sql,filelen+1);

    while (fgets(line, sizeof(line), fileptr) != NULL)
        strncat(sql, line, filelen - strlen(sql));

    if (strlen(sql) <= 0)
    {
       SendErrorMessage(hInstall,__("Could not read script file: %S"), filename);
       free(sql);
       fclose(fileptr);
       return 0;
    }

    fclose(fileptr);

    /* Execute it */
    res = PQexec(conn, "BEGIN;");
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        SendErrorMessage(hInstall,__("A database error occured whilst starting a transaction for '%S': %S"), filename, PQerrorMessage(conn));

        if (res)
            PQclear(res);
        
        free(sql);
        return 0;
    }
    
    res = PQexec(conn, sql);
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        SendErrorMessage(hInstall,__("A database command error occured whilst executing '%S': %S"), filename, PQerrorMessage(conn));
        
        PQexec(conn, "ROLLBACK;");
        
        if (res)
            PQclear(res);
        
        free(sql);
        return 0;
    }
    
    res = PQexec(conn, "COMMIT;");
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        SendErrorMessage(hInstall,__("A database command error occured whilst committing the transation for '%S': %S"), filename, PQerrorMessage(conn));
        
        PQexec(conn, "ROLLBACK;");
        if (res)
            PQclear(res);
        
        free(sql);
        return 0;
    }

    if (res)
        PQclear(res);

    free(sql);
    return 1;
}

static int CreateTemplatePostGISDatabase(MSIHANDLE hInstall, PGconn *conn)
{
    PGresult *res;

    /* Create the template_postgis database (CREATE DATABASE cannot be run within a transaction) */
    res = PQexec(conn, "CREATE DATABASE template_postgis");
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        SendErrorMessage(hInstall,__("An error occurred whilst creating the template_postgis database: %S"), PQerrorMessage(conn));
        
        if (res)
            PQclear(res);

        return 0;
    }

    /* Mark the database as a template */
    res = PQexec(conn, "UPDATE pg_database SET datistemplate = 't' WHERE datname = 'template_postgis'");
    if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        SendErrorMessage(hInstall,__("An error occurred whilst setting the template flag on the template_postgis database: %S"), PQerrorMessage(conn));
        
        if (res)
            PQclear(res);

        return 0;
    }

    return 1;
}

__declspec(dllexport)
UINT __stdcall InstallContrib(MSIHANDLE hInstall)
{
    char mybuf[8192];
    DWORD szbuf = sizeof(mybuf);
    char *langid, *superuser, *superpassword, *listenport, *scriptdir, *contribstr;
    char connstr[1024], connstr2[1024];
    int CONTRIB[CONTRIB_COUNT];
    int contrib;
    PGconn *conn, *conn2;
    int x;

    szbuf = sizeof(mybuf);
    ZeroMemory(mybuf,sizeof(mybuf));
    if (MsiGetPropertyA(hInstall, "CustomActionData", mybuf, &szbuf) != ERROR_SUCCESS)
    {
        SendErrorMessage(hInstall,L"Failed to retrieve the 'CustomActionData' property whilst preparing to activate contrib modules.");
        return ERROR_INSTALL_FAILURE;
    }

    langid = mybuf;
    superuser = strchr(langid,';');
    if (!superuser)
    {
        SendErrorMessage(hInstall,L"Malformed plinfo property: 'superuser'.");
        return ERROR_INSTALL_FAILURE;
    }
    *superuser = 0;
    superuser++;

    pginst_settext2(langid);

    superpassword = strchr(superuser,';');
    if (!superpassword)
    {
        SendErrorMessage(hInstall,L"Malformed InstallContrib property: 'superpassword'.");
        return ERROR_INSTALL_FAILURE;
    }
    *superpassword = 0;
    superpassword++;

    listenport = strchr(superpassword,';');
    if (!listenport)
    {
        SendErrorMessage(hInstall,L"Malformed InstallContrib property: 'listenport'.");
        return ERROR_INSTALL_FAILURE;
    }
    *listenport=0;
    listenport++;
    
    scriptdir = strchr(listenport,';');
    if (!scriptdir)
    {
        SendErrorMessage(hInstall,L"Malformed InstallContrib property: 'scriptdir'.");
        return ERROR_INSTALL_FAILURE;
    }
    *scriptdir=0;
    scriptdir++;
    
    contribstr = strchr(scriptdir,';');
    if (!contribstr)
    {
        SendErrorMessage(hInstall,L"Malformed InstallContrib property: 'contribstr'.");
        return ERROR_INSTALL_FAILURE;
    }
    *contribstr = 0;
    contribstr++;

    ZeroMemory(&CONTRIB,sizeof(CONTRIB));
    for (contrib = 0; contrib < CONTRIB_COUNT; contrib++)
    {
        char *contribnext = strchr(contribstr,';');
        if (!contribnext)
        {
            SendErrorMessage(hInstall,L"Malformed list of contrib modules!");
            return ERROR_INSTALL_FAILURE;
        }
        if (*contribstr == '1')
            CONTRIB[contrib]=1;
        contribstr = contribnext+1;
    }
    
    /* Ok, all info returned, now try to connect */
    sprintf(connstr,"hostaddr=127.0.0.1 port=%s dbname=template1 user='%s' password='%s'",listenport,superuser,superpassword);
    
    /* Try 10 connects with 1 second in between, to get around the
     * "database is starting up" error */
    for (x = 0; x < 10; x++)
    {
        Sleep(1000);
        conn = PQconnectdb(connstr);
        if (PQstatus(conn) == CONNECTION_OK)
            break;
    }
    if (PQstatus(conn) != CONNECTION_OK)
    {
        SendErrorMessage(hInstall,__("Failed to connect to the 'template1' database. Contrib files are installed, but are not activated in any databases."));
        SendErrorMessage(hInstall,L"%S",PQerrorMessage(conn));
        return ERROR_SUCCESS;
    }

    for (contrib = 0; contrib < CONTRIB_COUNT; contrib++)
    {
        if (CONTRIB[contrib])
        {
            if (!RunSqlScript(hInstall, conn, scriptdir, CONTRIBINFO[contrib].script))
            {
                SendErrorMessage(hInstall,__("Failed to install contrib module %S (%S) in the 'template1' database. The files are still installed, but are not activated in any database."), CONTRIBINFO[contrib].contribname, CONTRIBINFO[contrib].script);
            }

            /* The Adminpack module is a special case an must be installed in the postgres database for pgAdmin to find. */
            if (strcmp(CONTRIBINFO[contrib].contribname, "Adminpack") == 0)
            {
                sprintf(connstr2,"hostaddr=127.0.0.1 port=%s dbname=postgres user='%s' password='%s'",listenport,superuser,superpassword);
                conn2 = PQconnectdb(connstr2);

                if (PQstatus(conn2) != CONNECTION_OK)
                {
                    SendErrorMessage(hInstall,__("Failed to connect to the 'postgres' database. The %S module files are installed, but are not activated."), CONTRIBINFO[contrib].contribname);
                    SendErrorMessage(hInstall,L"%S",PQerrorMessage(conn2));
                }
                else
                {
                    if (!RunSqlScript(hInstall, conn2, scriptdir, CONTRIBINFO[contrib].script))
                    {
                        SendErrorMessage(hInstall,__("Failed to install the %S (%S) module in the 'postgres' database. The files are still installed, but are not activated."), CONTRIBINFO[contrib].contribname, CONTRIBINFO[contrib].script);
                    }
                }
                PQfinish(conn2);
            }
        }
    }
    
    PQfinish(conn);
    return ERROR_SUCCESS;
}

__declspec(dllexport)
UINT __stdcall InstallPostgis(MSIHANDLE hInstall)
{
    char mybuf[8192];
    DWORD szbuf = sizeof(mybuf);
    char *langid, *superuser, *superpassword, *listenport, *scriptdir;
    char connstr[1024];
    int x;
    PGconn *conn;
    PGresult *res;

    szbuf = sizeof(mybuf);
    ZeroMemory(mybuf,sizeof(mybuf));
    if (MsiGetPropertyA(hInstall, "CustomActionData", mybuf, &szbuf) != ERROR_SUCCESS)
    {
        SendErrorMessage(hInstall,L"Failed to retrieve the 'CustomActionData' property whilst preparing to activate PostGIS.");
        return ERROR_INSTALL_FAILURE;
    }

    langid = mybuf;
    superuser = strchr(langid,';');
    if (!superuser)
    {
        SendErrorMessage(hInstall,L"Malformed plinfo property: 'superuser'.");
        return ERROR_INSTALL_FAILURE;
    }
    *superuser = 0;
    superuser++;

    pginst_settext2(langid);

    superpassword = strchr(superuser,';');
    if (!superpassword)
    {
        SendErrorMessage(hInstall,L"Malformed InstallPostgis property: 'superpassword'.");
        return ERROR_INSTALL_FAILURE;
    }
    *superpassword = 0;
    superpassword++;

    listenport = strchr(superpassword,';');
    if (!listenport)
    {
        SendErrorMessage(hInstall,L"Malformed InstallPostgis property: 'listenport'.");
        return ERROR_INSTALL_FAILURE;
    }
    *listenport=0;
    listenport++;
    
    scriptdir = strchr(listenport,';');
    if (!scriptdir)
    {
        SendErrorMessage(hInstall,L"Malformed InstallPostgis property: 'scriptdir'.");
        return ERROR_INSTALL_FAILURE;
    }
    *scriptdir=0;
    scriptdir++;    

    sprintf(connstr,"hostaddr=127.0.0.1 port=%s dbname=postgres user='%s' password='%s'",listenport,superuser,superpassword);

    /* Try 10 connects with 1 second in between, to get around the
     * "database is starting up" error */
    for (x = 0; x < 10; x++)
    {
        Sleep(1000);
        conn = PQconnectdb(connstr);
        if (PQstatus(conn) == CONNECTION_OK)
            break;
    }
    if (PQstatus(conn) != CONNECTION_OK)
    {
        SendErrorMessage(hInstall,__("Failed to connect to the database. PostGIS files are installed, but are not activated in any databases."));
        SendErrorMessage(hInstall,L"%S",PQerrorMessage(conn));
        return ERROR_SUCCESS;
    }
    
    /* Create the template_postgis database and mark it as a template in pg_database */
    if (!CreateTemplatePostGISDatabase(hInstall, conn))
    {
        SendErrorMessage(hInstall,__("Failed to create the PostGIS template database. PostGIS files are installed, but the template_postgis database is not active."));
        return ERROR_SUCCESS;
    }

    /* Disconnect from the default database */
    PQfinish(conn);

    /* Reconnect to the template_postgis database (no need to attempt reconnections again
     * since we wouldn't have managed to get past the first connection attempt above if the
     * database was still starting up */
    sprintf(connstr,"hostaddr=127.0.0.1 port=%s dbname=template_postgis user='%s' password='%s'",listenport,superuser,superpassword);
    conn = PQconnectdb(connstr);
    if (PQstatus(conn) != CONNECTION_OK)
    {
        SendErrorMessage(hInstall,__("Failed to connect to the database. PostGIS files are installed, but the template database is not active."));
        SendErrorMessage(hInstall,L"%S",PQerrorMessage(conn));
        return ERROR_SUCCESS;
    }

    /* PostGIS requires PLPGSQL to be installed */
    /* Will error when CREATE LANGUAGE is called in template1, so ignore errors */
    res = PQexec(conn, "CREATE LANGUAGE plpgsql");
    if (res)
        PQclear(res);

    /* Load the main PostGIS functions */
    if (!RunSqlScript(hInstall, conn, scriptdir, "lwpostgis.sql"))
    {
        SendErrorMessage(hInstall,__("Failed to enable PostGIS. The files are still installed, but are not activated in any database."));
        PQfinish(conn);
        return ERROR_SUCCESS;
    }

    /* Load the spatial reference systems */
    if (!RunSqlScript(hInstall, conn, scriptdir, "spatial_ref_sys.sql"))
    {
        SendErrorMessage(hInstall,__("Failed to load spatial reference systems. The files are still installed, but are not activated in any database."));
        PQfinish(conn);
        return ERROR_SUCCESS;
    }

    /* VACUUM the spatial_ref data. This is required because it can't be done in the script */
    /* because PG83 and above don't allow VACUUM in a multi-statement. Ignore any errors. */
    res = PQexec(conn, "VACUUM ANALYZE spatial_ref_sys;");
    if (res)
        PQclear(res);

    PQfinish(conn);
    return ERROR_SUCCESS;
}

/* Create service account */
__declspec(dllexport)
UINT __stdcall DoCreateUser(MSIHANDLE hInstall)
{
    char mybuf[8192];
    char *langid, *account, *domain, *password;
    DWORD szbuf;
    WCHAR errmsg[512];
    
    szbuf = sizeof(mybuf);
    ZeroMemory(mybuf,sizeof(mybuf));
    if (MsiGetPropertyA(hInstall, "CustomActionData", mybuf, &szbuf) != ERROR_SUCCESS)
    {
        SendErrorMessage(hInstall,L"Failed to retreive the 'CustomAction Data' property whilst preparing to create user.");
        return ERROR_INSTALL_FAILURE;
    }

    langid = mybuf;
    domain = strchr(langid,';');
    if (!domain)
        return ERROR_INSTALL_FAILURE;
    *domain = 0;
    domain++;

    pginst_settext2(langid);

    account = strchr(domain,';');
    if (!account)
        return ERROR_INSTALL_FAILURE;
    *account=0;
    account++;

    password = strchr(account,';');
    if (!password)
        return ERROR_INSTALL_FAILURE;
    *password = 0;
    password++;

    if (strlen(account) > 20)
    {
        SendErrorMessage(hInstall,__("Specified username is too long. Maximum 20 characters is permitted for local user names."));
        return ERROR_INSTALL_FAILURE;
    }

    if (!CreateUser(domain, account, password, errmsg, sizeof(errmsg)/sizeof(WCHAR) - 1))
    {
        errmsg[sizeof(errmsg)/sizeof(WCHAR)-1] = 0;
        SendErrorMessage(hInstall, errmsg);
        return ERROR_INSTALL_FAILURE;
    }

    if (!CheckUserPrivileges(domain, account, TRUE, errmsg, sizeof(errmsg)/sizeof(WCHAR) - 1))
    {
        errmsg[sizeof(errmsg)/sizeof(WCHAR)-1] = 0;
        SendErrorMessage(hInstall, errmsg);
        return ERROR_INSTALL_FAILURE;
    }

    return ERROR_SUCCESS;
}

/* Verify that the specified user account exists and can be used. 
   (we do some verification in the UI sequence as well, but we need
   one in the Execute sequence as well in case of silent installs */
__declspec(dllexport)
UINT __stdcall DoVerifyUser(MSIHANDLE hInstall)
{
    char mybuf[8192];
    char *langid, *account, *domain, *password;
    DWORD szbuf;
    WCHAR errmsg[512];

    ZeroMemory(errmsg,sizeof(errmsg));
    
    szbuf = sizeof(mybuf);
    ZeroMemory(mybuf,sizeof(mybuf));
    if (MsiGetPropertyA(hInstall, "CustomActionData", mybuf, &szbuf) != ERROR_SUCCESS)
    {
        SendErrorMessage(hInstall,L"Failed to retreive the 'CustomAction Data' property whilst preparing to create user.");
        return ERROR_INSTALL_FAILURE;
    }

    langid = mybuf;
    domain = strchr(langid,';');
    if (!domain)
        return ERROR_INSTALL_FAILURE;
    *domain = 0;
    domain++;

    pginst_settext2(langid);

    account = strchr(domain,';');
    if (!account)
        return ERROR_INSTALL_FAILURE;
    *account=0;
    account++;

    password = strchr(account,';');
    if (!password)
        return ERROR_INSTALL_FAILURE;
    *password = 0;
    password++;

    if (!CheckUserExists(domain, account))
    {
        SendErrorMessage(hInstall, __("User account %S\\%S does not exist"),domain,account);
        return ERROR_INSTALL_FAILURE;
    }

    if (!CheckUserPrivileges(domain, account, TRUE, errmsg, sizeof(errmsg)/sizeof(WCHAR)-1))
    {
        SendErrorMessage(hInstall, errmsg);
        return ERROR_INSTALL_FAILURE;
    }

    if (!TestLogin(domain, account, password, errmsg, sizeof(errmsg)/sizeof(WCHAR)-1))
    {
        SendErrorMessage(hInstall, errmsg);
        return ERROR_INSTALL_FAILURE;
    }

    return ERROR_SUCCESS;
}

/* Send browser to mailinglist subscription page */
static const char *browserurl="http://mail.postgresql.org/mj/mj_wwwusr?domain=postgresql.org&func=lists-long-full&extra=pgsql-announce";

__declspec(dllexport)
UINT __stdcall SubscribeList(MSIHANDLE hInstall)
{
    int r;

    pginst_settext(hInstall);

    r = (int)ShellExecute(NULL, "open", browserurl, NULL, NULL, SW_SHOWNORMAL);
    if (r < 32)
    {
        SendErrorMessage(hInstall, __("Failed to open the default web browser! You will need to subscribe manually; see http://www.postgresql.org/ for details."));
    }
    return ERROR_SUCCESS;
}
